#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <semaphore.h>

#ifndef UTIL_H
#define UTIL_H

int get_user_count() {
    char* root = getenv("RELAT_ROOT");

    int count = 0;
    DIR* dirp;
    struct dirent* entry;

    if ((dirp = opendir(root)) == NULL) {
        perror("could not open root dir");
        exit(-1);
    }

    while ((entry = readdir(dirp)) != NULL) {
        char entry_path[256];
        sprintf(entry_path, "%s/%s", root, entry->d_name);

        struct stat* entry_stat;
        if (stat(entry_path, entry_stat) != 0) {
            perror("Could not read stats from entry");
            exit(-1);
        }

        if (!S_ISDIR(entry_stat->st_mode)) {
            continue;
        }

        // TODO: better check whether directory is really a user
        if (entry->d_namlen < 3) {
            continue;
        }

        count++;
    }

    return count;
}

unsigned long get_unix_timestamp() {
    struct timeval tv;
    gettimeofday(&tv, NULL);

    return (unsigned long)(tv.tv_sec) * 1000 + (unsigned long)(tv.tv_usec) / 1000;
}

void create_unit(char* user, char* unit, float x, float y) {
    char* root = getenv("RELAT_ROOT");

    char unit_directory[256];
    sprintf(unit_directory, "%s/%s/%s", root, user, unit);
    if (mkdir(unit_directory) != 0) {
        perror("mkdir failed");
        exit(-1);
    }
    
    char unit_pos_file_name[256];
    sprintf(unit_pos_file_name, "%s/%s/%s/position", root, user, unit);
    FILE* unit_pos_file;
    if ((unit_pos_file = fopen(unit_pos_file_name, "w")) == NULL) {
        perror("could not create position file for unit");
        exit(-1);
    }

    fprintf(unit_pos_file, "%.3f %.3f %.3f\n", x, y, 0.0f);
    fclose(unit_pos_file);

    char unit_move_file_name[256];
    sprintf(unit_move_file_name, "%s/%s/%s/move", root, user, unit);
    FILE* unit_move_file;
    if ((unit_move_file = fopen(unit_move_file_name, "w")) == NULL) {
        perror("could not create move file for unit");
        exit(-1);
    }

    fprintf(unit_move_file, "%ul %ul %.3f %.3f %.3f", 0l, ULONG_MAX, 0.0f, 0.0f, 0.0f);
}

unsigned generate_user_id(char* root) {
    char user_count_file_name[256];
    sprintf(user_count_file_name, "%s/user_count", root);

    int user_count_fd;
    if ((user_count_fd = open(user_count_file_name, O_RDWR)) == -1) {
        perror("could not open user_count file");
        exit(-1);
    }

    unsigned* id = (unsigned*)mmap(NULL, sizeof(unsigned), PROT_WRITE, MAP_SHARED, user_count_fd, 0);
    if (id == (unsigned*)-1) {
        perror("could not mmap user_count file");
        exit(-1);
    }

    int current_id = *id;
    (*id)++;

    if (munmap(id, sizeof(unsigned)) == -1) {
        perror("could not munmap user_count file");
        exit(-1);
    }
    close(user_count_fd);

    return current_id;
}

unsigned generate_unit_id(char* root) {
    char unit_count_file_name[256];
    sprintf(unit_count_file_name, "%s/unit_count", root);

    int unit_count_fd;
    if ((unit_count_fd = open(unit_count_file_name, O_RDWR)) == -1) {
        perror("could not open unit_count file");
        exit(-1);
    }

    unsigned* id = (unsigned*)mmap(NULL, sizeof(unsigned), PROT_WRITE, MAP_SHARED, unit_count_fd, 0);
    if (id == (unsigned*)-1) {
        perror("could not mmap unit_count file");
        exit(-1);
    }

    int current_id = *id;
    (*id)++;

    if (munmap(id, sizeof(unsigned)) == -1) {
        perror("could not munmap unit_count file");
        exit(-1);
    }
    close(unit_count_fd);

    return current_id;
}

void set_position(char* root, char* target, unsigned id, double x, double y, double z) {
    char position_file_name[256];
    sprintf(position_file_name, "%s/%s/position", root, target);

    int position_fd;
    if ((position_fd = open(position_file_name, O_RDWR)) == -1) {
        perror("could not open position file");
        exit(-1);
    }

    double* vector = (double*)mmap(NULL, (id + 1) * sizeof(double) * 3, PROT_WRITE, MAP_SHARED, position_fd, 0);
    if (vector == (double*)-1) {
        perror("could not mmap position file");
        exit(-1);
    }

    vector[id * 3] = x;
    vector[id * 3 + 1] = y;
    vector[id * 3 + 2] = z;

    if (munmap(vector, sizeof(double) * 3) == -1) {
        perror("could not munmap position file");
        exit(-1);
    }
    close(position_fd);
}

void set_user_position(char* root, unsigned id, double x, double y, double z) {
    set_position(root, "user", id, x, y, z);
}

void set_unit_position(char* root, unsigned id, double x, double y, double z) {
    set_position(root, "unit", id, x, y, z);
}

void get_position(char* root, char* target, unsigned id, double* x, double* y, double* z) {
    char position_file_path[256];
    sprintf(position_file_path, "%s/%s/position", root, target);

    int position_fd;
    if ((position_fd = open(position_file_path, O_RDONLY)) == -1) {
        perror("could not open position file");
        exit(-1);
    }

    char semaphore_file_path[256];
    sprintf(semaphore_file_path, "%s/.semaphore/position_%s_%u", root, target, id);

    // int sem = sem_open(semaphore_file_path, O_CREAT, 0677, 1);
    // printf("%i\n", sem);
    // if (sem == -1) {
    //     perror("could not open semaphore");
    //     exit(-1);
    // }

    double* position = (double*)mmap(NULL, (id + 1) * sizeof(double) * 3, PROT_READ, MAP_SHARED, position_fd, 0);
    if (position == (double*)-1) {
        perror("could not mmap position file");
        exit(-1);
    }
    
    //sem_wait(sem);
    *x = position[id * 3];
    *y = position[id * 3 + 1];
    *z = position[id * 3 + 2];
    //sem_post(sem);

    if (munmap(position, (id + 1) * sizeof(double) * 3) == -1) {
        perror("could not munmap position file");
        exit(-1);
    }
    //sem_close(sem);
    close(position_fd);
}

void get_user_position(char* root, unsigned id, double* x, double* y, double* z) {
    get_position(root, "user", id, x, y, z);
}

void get_unit_position(char* root, unsigned id, double* x, double* y, double* z) {
    get_position(root, "unit", id, x, y, z);
}

void set_name(char* root, char* target, unsigned id, char* new_name) {
    char name_file_name[256];
    sprintf(name_file_name, "%s/%s/name", root, target);

    int name_fd;
    if ((name_fd = open(name_file_name, O_RDWR)) == -1) {
        perror("could not open name file");
        exit(-1);
    }

    char* name = (char*)mmap(NULL, (id + 1) * sizeof(char) * 64, PROT_WRITE, MAP_SHARED, name_fd, 0);
    if (name == (char*)-1) {
        perror("could not mmap name file");
        exit(-1);
    }
    
    strcpy(name + id * 64, new_name);

    if (munmap(name, sizeof(char) * 64) == -1) {
        perror("could not munmap name file");
        exit(-1);
    }
    close(name_fd);
}

void set_user_name(char* root, unsigned id, char* new_name) {
    set_name(root, "user", id, new_name);
}

void set_unit_name(char* root, unsigned id, char* new_name) {
    set_name(root, "unit", id, new_name);
}

void get_name(char* root, char* target, unsigned id, char* return_name) {
    char name_file_name[256];
    sprintf(name_file_name, "%s/%s/name", root, target);

    int name_fd;
    if ((name_fd = open(name_file_name, O_RDONLY)) == -1) {
        perror("could not open name file");
        exit(-1);
    }

    char* name = (char*)mmap(NULL, (id + 1) * sizeof(char) * 64, PROT_READ, MAP_SHARED, name_fd, 0);
    if (name == (char*)-1) {
        perror("could not mmap name file");
        exit(-1);
    }
    
    strcpy(return_name, name + id * sizeof(char) * 64);

    if (munmap(name, (id + 1) * sizeof(char) * 64) == -1) {
        perror("could not munmap name file");
        exit(-1);
    }
    close(name_fd);
}

void get_user_name(char* root, unsigned id, char* return_name) {
    get_name(root, "user", id, return_name);
}

void get_unit_name(char* root, unsigned id, char* return_name) {
    get_name(root, "unit", id, return_name);
}

void set_unit_owner(char* root, unsigned id, unsigned owner_id) {
    char owner_file_name[256];
    sprintf(owner_file_name, "%s/unit/owner", root);

    int owner_fd;
    if ((owner_fd = open(owner_file_name, O_RDWR)) == -1) {
        perror("could not open owner file");
        exit(-1);
    }

    unsigned* owner = (unsigned*)mmap(NULL, (id + 1) * sizeof(unsigned), PROT_WRITE, MAP_SHARED, owner_fd, 0);
    if (owner == (unsigned*)-1) {
        perror("could not mmap owner file");
        exit(-1);
    }
    
    owner[id] = owner_id;

    if (munmap(owner, sizeof(unsigned) * 64) == -1) {
        perror("could not munmap owner file");
        exit(-1);
    }
    close(owner_fd);
}

unsigned create_unit_awoo(char* root, unsigned user_id, char* name, double x, double y) {
    unsigned unit_id = generate_unit_id(root);

    set_unit_name(root, unit_id, name);
    set_unit_position(root, unit_id, x, y, 0);
    set_unit_owner(root, unit_id, user_id);
}

#endif
